#include "freyabasepluginex.h"
#include <QtConcurrent>

FreyaBasePluginEx::FreyaBasePluginEx(QHostAddress platformAddr, int port, FreyaBaseControl *pControl, const QString &PluginName) :
    QThread(nullptr)
    , m_addPlatform(platformAddr)
    , m_iPort(port)
    , m_PluginSocket(nullptr)
    , m_PluginServer(nullptr)
    , m_Pusher(nullptr)
    , m_pControl(pControl)
    , m_MsgAuth(0)
    , m_CmdAuth(0)
{
    setObjectName(PluginName);
    moveToThread(this);
    start();

    connect(this, SIGNAL(ToConnectToExtension()), SLOT(OnConnectToExtension()));

    // NOOP包用于检查TCP连接状态。仅在无消息通信时触发
    // NOOP触发间隔为5秒，断联检测大概需要4个NOOP包。也就是20秒左右。
    // 由于有消息通信时断联会由通信触发断联事件。故，认为此处NOOP逻辑不用太灵敏。
    m_NoopTimer.setInterval(5000);
    connect(&m_NoopTimer, SIGNAL(timeout()), this, SLOT(OnNoopTimeout()));

    m_ReplyDelayTimer.setSingleShot(true);
    connect(&m_ReplyDelayTimer, SIGNAL(timeout()), this, SLOT(OnDelayTimeout()), Qt::DirectConnection);
}

FreyaBasePluginEx::~FreyaBasePluginEx()
{
    DisconnectToExtension();
    if(nullptr != m_PluginSocket)
    {
        m_PluginSocket->deleteLater();
    }
    if(m_bProcesslogFile.isOpen())
    {
        m_bProcesslogFile.close();
    }
    quit();
    wait();
}

bool FreyaBasePluginEx::IsPluginConnected()
{
    return (nullptr != m_Pusher);
}

int FreyaBasePluginEx::PluginMSGAuth() const
{
    return m_MsgAuth;
}

int FreyaBasePluginEx::PluginCMDAuth() const
{
    return m_CmdAuth;
}

QString FreyaBasePluginEx::PluginID() const
{
    return m_PluginId;
}

void FreyaBasePluginEx::ConnectToExtension()
{
    emit ToConnectToExtension();
}

void FreyaBasePluginEx::DisconnectToExtension()
{
    if(nullptr != m_Pusher)
    {
        m_Pusher->DisconnectFromHost();
        qDebug() << "FreyaLib > " << "Plugin:" << m_Pusher->PeerAddress() << "disconnect:" << m_Pusher->WaitForDisconnected(1000);
    }
}

bool FreyaBasePluginEx::ImportPluginAuth(const QStringList &MsgCode, const QStringList &CmdCode)
{
    if(nullptr != m_Pusher)
    {
        qDebug() << "FreyaLib > " << "FreyaBasePluginEx: ImportPluginAuth" << MsgCode.size() << CmdCode.size();
        FreyaData pData = FreyaBaseData::CreateData(FREYALIB_CMD_PLUGINAUTHREQUEST);
        QVariantMap dataMap;
        dataMap.insert(FREYALIB_TYP_PLUGINMSGAUTH, MsgCode);
        dataMap.insert(FREYALIB_TYP_PLUGINCMDAUTH, CmdCode);
        pData->SetProperty(dataMap);
        return WriteToPusher(pData);
    }
    return false;
}

void FreyaBasePluginEx::NeedNOOP(bool isNeed)
{
    if(isNeed)
    {
        QTimer::singleShot(0, &m_NoopTimer, SLOT(start()));
    }
    else
    {
        QTimer::singleShot(0, &m_NoopTimer, SLOT(stop()));
    }
}

void FreyaBasePluginEx::NeedProcesslog(bool isNeed, const QString &path)
{
    if(isNeed)
    {
        QString strLogPath = path + "/plog/";
        bool bPath = CheckFilePath(strLogPath);

        m_bProcesslogFile.setFileName(QString(strLogPath + "plog_%1.log").arg(QDate::currentDate().toString("yyyy_MM_dd")));
        bool bFileOpen = m_bProcesslogFile.open(QFile::WriteOnly | QFile::Append);

        qDebug() << "FreyaLib > " << "Need process log" << m_bProcesslogFile.fileName() << bPath << bFileOpen;
    }
}

bool FreyaBasePluginEx::PluginWrite(const quint64 &command, int msec)
{
    FreyaData pData = FreyaBaseData::CreateData(command);
    return PluginWrite(pData, msec);
}

bool FreyaBasePluginEx::PluginWrite(FreyaData &pData, int msec)
{
    bool ret = false;

    //是否为私有消息，非插件消息
    if(pData.isNull() || pData->IsLocalData())
    {
        return ret;
    }

    if(m_Pusher)
    {
        pData->SetProperty(FREYALIB_FLG_PLUGINID, m_PluginId);
        pData->SetProperty(FREYALIB_FLG_WAITTIME, msec);
        qDebug() << "FreyaLib > " << "FreyaBasePluginEx: PluginWriteData Cmd:" << m_pControl->CmdLogString(pData->command) << "DataId:" << pData->dataID;

        AddQueryData(pData, msec);
        ret = WriteToPusher(pData);
    }
    return ret;
}

// 检查文件所在路径
bool FreyaBasePluginEx::CheckFilePath(const QString &filePath)
{
    QDir dirAlgData(filePath);
    return (dirAlgData.mkpath(filePath) && dirAlgData.exists());
}

void FreyaBasePluginEx::OnPusherQuery(const FreyaData pData, int msec, FreyaBasePluginEx *object, FreyaBaseControl *pControl)
{
    // 如果总线操作返回false，可能不是向该插件查询，因此不回复
    if(pControl->RequestExecution(pData, object, msec))
    {
        object->WriteToPusher(pData);
    }
}

void FreyaBasePluginEx::AddQueryData(const FreyaData pData, int msec)
{
    if(msec)
    {
        CheckSysTime();

        QMutexLocker locker(&m_mutexQuery);
        m_hashQueryData.insert(pData->dataID, pData);

        QDateTime deadLine = QDateTime::currentDateTime().addMSecs(msec);
        if(deadLine.isValid())
        {
            QStringList listQueryId;
            if(m_mapQueryDelay.contains(deadLine))
            {
                listQueryId = m_mapQueryDelay.value(deadLine);
            }
            listQueryId.append(pData->dataID);
            m_mapQueryDelay.insert(deadLine, listQueryId);
        }

        int interval = QDateTime::currentDateTime().msecsTo(m_mapQueryDelay.firstKey());
        m_ReplyDelayTimer.setInterval(interval > 0 ? interval : 0);
        m_ReplyDelayTimer.start();
    }
}

bool FreyaBasePluginEx::CheckSysTime()
{
    bool bRet = false;
    QMutexLocker locker(&m_mutexQuery);
    if(!m_mapQueryDelay.isEmpty())
    {
        if(m_ReplyDelayTimer.isActive() && 0 < m_ReplyDelayTimer.remainingTime())
        {
            int diversity = m_ReplyDelayTimer.remainingTime() - (QDateTime::currentDateTime().msecsTo(m_mapQueryDelay.firstKey()));
            if(200 < qAbs(diversity))
            {
                bRet = true;
                qDebug() << "FreyaLib > " << "FreyaBasePluginEx: System time is changed!" << diversity;
                QMap<QDateTime, QStringList> mapQueryDelay(m_mapQueryDelay);
                m_mapQueryDelay.clear();
                QMapIterator<QDateTime, QStringList> mapItQueryDelay(mapQueryDelay);
                while(mapItQueryDelay.hasNext())
                {
                    mapItQueryDelay.next();
                    m_mapQueryDelay.insert(mapItQueryDelay.key().addMSecs(diversity), mapItQueryDelay.value());
                }
            }
        }
    }

    return bRet;
}

void FreyaBasePluginEx::OnConnectToExtension()
{
    while(!WaitForConnect())
    {
        qWarning() << "FreyaLib > " << "Cannot connect to extension:" << m_addPlatform << "try again soon";
        QThread::msleep(2000);
    }
}

bool FreyaBasePluginEx::WaitForConnect()
{
    if(nullptr == m_PluginSocket)
    {
        m_PluginSocket = new FTcpSocket();
        connect(m_PluginSocket, SIGNAL(SigPackData(QByteArray)), this, SLOT(OnReadyRead(QByteArray)));
    }

    m_PluginSocket->ConnectToHost(m_addPlatform, m_iPort);
    if(m_PluginSocket->WaitForConnected())
    {
        QThread::sleep(1); // wait for server connected
        qDebug() << "FreyaLib > " << "FreyaBasePluginEx: Request connected!";
        FreyaData pData = FreyaBaseData::CreateData(FREYALIB_CMD_PLUGINREQUEST);
        if(!pData.isNull())
        {
            m_PluginSocket->Write(pData->Serialize());
            m_PluginSocket->WaitForBytesWritten();
//            m_PluginSocket->WaitForReadyRead();
        }

        return true;
    }
    else
    {
        return false;
    }
}

bool FreyaBasePluginEx::WriteToPusher(const FreyaData pData)
{
    QMetaObject::invokeMethod(this, "OnPluginWrite", Q_ARG(FreyaData, pData));
    return true;
}

void FreyaBasePluginEx::Processlog(const QByteArray &data)
{
    m_bProcesslogFile.write(QTime::currentTime().toString("HH:mm:ss.zzz\n").toUtf8());
    QString dataId;
    QDataStream stream(data);
    QVariantMap varMap;
    stream >> dataId >> varMap;
    m_bProcesslogFile.write(dataId.toUtf8());
    m_bProcesslogFile.write("\n");
    quint64 cmd = varMap.value(FREYALIB_TYP_CMD).toULongLong();
    m_bProcesslogFile.write((QString("0x" + QString::number(cmd, 16).rightJustified(16, '0'))).toUtf8());
    m_bProcesslogFile.write("_");
    m_bProcesslogFile.write(m_pControl->CmdLogString(cmd).toUtf8());
    m_bProcesslogFile.write("\n");
    m_bProcesslogFile.write(QJsonDocument::fromVariant(varMap.value(FREYALIB_TYP_ALLVALUE).toMap()).toJson());
    m_bProcesslogFile.write("\n");
    m_bProcesslogFile.flush();
}

void FreyaBasePluginEx::Processlog(const FreyaData pData)
{
    m_bProcesslogFile.write(QTime::currentTime().toString("HH:mm:ss.zzz\n").toUtf8());
    m_bProcesslogFile.write(pData->dataID.toUtf8());
    m_bProcesslogFile.write("\n");
    m_bProcesslogFile.write((QString("0x" + QString::number(pData->command, 16).rightJustified(16, '0'))).toUtf8());
    m_bProcesslogFile.write("_");
    m_bProcesslogFile.write(m_pControl->CmdLogString(pData->command).toUtf8());
    m_bProcesslogFile.write("\n");
    m_bProcesslogFile.write(QJsonDocument::fromVariant(pData->GetProperty(FREYALIB_TYP_ALLVALUE)).toJson());
    m_bProcesslogFile.write("\n");
    m_bProcesslogFile.flush();
}

void FreyaBasePluginEx::OnReadyRead(const QByteArray &byteData)
{
    FreyaData pData = FreyaBaseData::CreateData();

    if(!pData->Unserialize(byteData))
    {
        qWarning() << "FreyaLib > " << "OnReadyRead: cannot unserialize data: " << byteData.toHex();
        return;
    }

    qDebug() << "FreyaLib > " << "FreyaBasePluginEx recv result:" << m_pControl->CmdLogString(pData->command);
    if(FREYALIB_CMD_PLUGINRESULT == pData->command)
    {
        m_PluginId = pData->GetProperty().toString();

        if(nullptr == m_PluginServer)
        {
            m_PluginServer = new QTcpServer(this);
            connect(m_PluginServer, SIGNAL(newConnection()), this, SLOT(OnPusherConnected()));
        }

        m_PluginServer->listen();
        if(m_PluginServer->isListening())
        {
            FreyaData pConnectData = FreyaBaseData::CreateData(FREYALIB_CMD_CONNECTREQUEST);
            pConnectData->SetProperty(m_PluginId);
            pConnectData->SetProperty(FREYALIB_FLG_PLUGINPORT, m_PluginServer->serverPort());

            if(nullptr != m_PluginSocket)
            {
                m_PluginSocket->Write(pConnectData->Serialize());
                m_PluginSocket->WaitForBytesWritten();
            }
        }
    }
}

void FreyaBasePluginEx::OnPusherConnected()
{
    if(nullptr == m_Pusher && nullptr != m_PluginServer)
    {
        QTcpSocket *pSocket = m_PluginServer->nextPendingConnection();
        m_Pusher = new FTcpSocket(pSocket);
        connect(m_Pusher, SIGNAL(SigPackData(QByteArray)), this, SLOT(OnPluginReadyRead(QByteArray)));
        connect(m_Pusher, SIGNAL(Disconnected()), this, SLOT(OnDisconnected()));
        m_PluginServer->close();
    }
    else
    {
        qWarning() << "FreyaLib > " << "FreyaBasePluginEx: Pusher is connected!";
    }
}

void FreyaBasePluginEx::OnDisconnected()
{
    if(nullptr != m_Pusher)
    {
        m_Pusher->deleteLater();
        m_Pusher = nullptr;
    }

    qDebug() << "FreyaLib > " << "plugin connect lost:" << m_PluginId << "retry to connect to extension:" << m_addPlatform;
    m_pControl->RequestExecution(FREYALIB_CMD_PLUGINCUTED);

    emit ToConnectToExtension();
}

void FreyaBasePluginEx::OnPluginReadyRead(const QByteArray &byteData)
{
    FreyaData pData = FreyaBaseData::CreateData();
    QByteArray *pBa = new QByteArray(byteData);

#ifdef EXTTRANS_ASYNC
    SerializeTask *pUnserializeTask = new SerializeTask(pBa, pData, false);
    connect(pUnserializeTask, SIGNAL(toTaskFinished(QByteArray*, FreyaData, bool)), this, SLOT(OnPluginReadyRead(QByteArray*, FreyaData, bool)));
    QThreadPool::globalInstance()->start(pUnserializeTask);
#else
    bool bRet = pData->Unserialize(*pBa);
    OnPluginReadyRead(pBa, pData, bRet);
#endif

    if(m_NoopTimer.isActive())
    {
        QTimer::singleShot(0, &m_NoopTimer, SLOT(start()));
    }
}

void FreyaBasePluginEx::OnPluginReadyRead(QByteArray *pBa, FreyaData pData, bool pBRet)
{
    if(nullptr != pBa)
    {
        delete pBa;
        pBa = nullptr;
    }

    if(m_bProcesslogFile.isOpen())
    {
        m_bProcesslogFile.write("E->P_");
        Processlog(pData);
    }

    if(!pBRet)
    {
        qWarning() << "FreyaLib > " << "OnPluginReadyRead: cannot unserialize data";
        return;
    }

    switch (pData->command)
    {
    case FREYALIB_CMD_CONNECTRESULT:
    {
        qDebug() << "FreyaLib > " << "FreyaBasePluginEx: Plugin connect success!";
        emit ToPluginConnected(IsPluginConnected());
    }
    break;
    case FREYALIB_CMD_PLUGINAUTHREPLAY:
    {
        QVariantMap AuthMap = pData->GetProperty().toMap();
        m_MsgAuth = AuthMap.value(FREYALIB_TYP_PLUGINMSGAUTH).toInt();
        m_CmdAuth = AuthMap.value(FREYALIB_TYP_PLUGINCMDAUTH).toInt();

        m_pControl->RequestExecution(FREYALIB_CMD_PLUGINCONNECTED);
    }
    break;
    case FREYALIB_CMD_EXTCHECK:
    {
//        PusherExecute(FreyaBaseData::CreateData(FREYALIB_CMD_EXTCHECK));
    }
    break;
    default:
    {
        bool bIsContains = false;
        {
            QMutexLocker locker(&m_mutexQuery);
            bIsContains = m_hashQueryData.contains(pData->dataID);
        }
        if(bIsContains)
        {
            CheckSysTime();

            QMutexLocker locker(&m_mutexQuery);

            QMapIterator<QDateTime, QStringList> mapItQueryDelay(m_mapQueryDelay);

            while(mapItQueryDelay.hasNext())
            {
                mapItQueryDelay.next();
                if(mapItQueryDelay.value().contains(pData->dataID))
                {
                    QStringList firstValue = m_mapQueryDelay.take(mapItQueryDelay.key());
                    firstValue.removeOne(pData->dataID);
                    if(!firstValue.isEmpty())
                    {
                        m_mapQueryDelay.insert(mapItQueryDelay.key(), firstValue);
                    }
                    if(!m_mapQueryDelay.isEmpty())
                    {
                        m_ReplyDelayTimer.setInterval(QDateTime::currentDateTime().msecsTo(m_mapQueryDelay.firstKey()));
                        QTimer::singleShot(0, &m_ReplyDelayTimer, SLOT(start()));
                    }
                    break;
                }
            }

            FreyaData pQueryData = m_hashQueryData.take(pData->dataID);
            pQueryData->DestoryArguments();
            pQueryData->SetProperty(pData->GetProperty());
            pQueryData->SetProperty(FREYALIB_FLG_ARGS, pData->GetProperty(FREYALIB_FLG_ARGS));
            emit ToPusherReply(pQueryData);
            return;
        }

        if(m_PluginId != pData->GetProperty(FREYALIB_FLG_PLUGINID).toString())
        {
            // 从宿主分发的消息
            pData->SetProperty(FREYALIB_FLG_PLUGINID, m_PluginId);
            // msec不为0，则是宿主向插件查询
            int msec = pData->GetProperty(FREYALIB_FLG_WAITTIME).toInt();
            if(msec)
            {
                auto ret = QtConcurrent::run(OnPusherQuery, pData, msec, this, m_pControl);
                Q_UNUSED(ret)
            }
            else
            {
                m_pControl->RequestExecution(pData, this);
            }
        }
    }
    break;
    }
}

void FreyaBasePluginEx::OnPluginWrite(const FreyaData pData)
{
    if(nullptr != m_Pusher)
    {
        pData->SetProperty(FREYALIB_FLG_PLUGINID, m_PluginId);
        QByteArray *pBa = new QByteArray();

#ifdef EXTTRANS_ASYNC
        SerializeTask *pSerializeTask = new SerializeTask(pBa, pData, true);
        connect(pSerializeTask, SIGNAL(toTaskFinished(QByteArray*, FreyaData, bool)), this, SLOT(OnPluginWrite(QByteArray*, FreyaData, bool)));
        QThreadPool::globalInstance()->start(pSerializeTask);
#else
        *pBa = pData->Serialize();
        OnPluginWrite(pBa, pData, true);
#endif
    }
}

void FreyaBasePluginEx::OnPluginWrite(QByteArray *pBa, FreyaData pData, bool pBRet)
{
    if(nullptr == pBa)
    {
        return;
    }
    if(pBRet)
    {
        if(m_bProcesslogFile.isOpen())
        {
            m_bProcesslogFile.write("P->E_");
            Processlog(pData);
        }

        m_Pusher->Write(*pBa);
        m_Pusher->WaitForBytesWritten();
    }
    delete pBa;
    pBa = nullptr;
    qDebug() << "FreyaLib > " << "FreyaBasePluginEx: OnPluginWrite:" << m_pControl->CmdLogString(pData->command) << pBRet;
}

void FreyaBasePluginEx::OnNoopTimeout()
{
    WriteToPusher(FreyaBaseData::CreateData(FREYALIB_CMD_PLUGINCHECK));
}

void FreyaBasePluginEx::OnDelayTimeout()
{
    CheckSysTime();

    QMutexLocker locker(&m_mutexQuery);

    if(!m_mapQueryDelay.isEmpty())
    {
        QStringList listQueryId = m_mapQueryDelay.take(m_mapQueryDelay.firstKey());
        foreach(const QString &str, listQueryId)
        {
            qWarning() << "FreyaLib > " << "FreyaBasePluginEx query timeout:" << str;
            FreyaData pRepData = m_hashQueryData.take(str);
            pRepData->SetProperty(FREYALIB_FLG_WAITTIME, -1); // 超时标记
            emit ToPusherReply(pRepData);
        }
    }

    if(!m_mapQueryDelay.isEmpty())
    {
        int interval = QDateTime::currentDateTime().msecsTo(m_mapQueryDelay.firstKey());
        m_ReplyDelayTimer.setInterval(interval > 0 ? interval : 0);
        m_ReplyDelayTimer.start();
    }
    else
    {
        m_ReplyDelayTimer.setInterval(0);
    }
}
